package cn.sdc.sfw.dao.model;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.sdc.sfw.dao.exception.DaoException;
import cn.sdc.sfw.exception.ReflectException;
import cn.sdc.sfw.util.ClassUtil;
import cn.sdc.sfw.util.ConstructorUtil;
import cn.sdc.sfw.util.MethodUtil;
import cn.sdc.sfw.util.StringUtil;
import cn.sdc.sfw.util.TypeUtil;

public class Bean {

	private static final Object[] EMPTY_ARGS = new Object[0];
	private Class beanClass_;
	private Constructor[] constructors_;
	private ResultMap propertyCache_ = new ResultMap();
	private Map methodsCache_ = new HashMap();
	private Map fieldCache_ = new ResultMap();
	private transient Set invalidPropertyNames_ = new HashSet(); 

	/**
	 * 
	 */
	public Bean(Class beanClass) throws DaoException {
		if (beanClass == null) {
			throw new DaoException(DaoException.BEAN_CLASS_NULL,new Object[]{beanClass});
		}
		beanClass_ = beanClass;
		constructors_ = beanClass.getConstructors();
		setupPropertys();
		setupMethods();
		setupField();
	}

	/**
	 * @see beans.BeanDesc#getBeanClass()
	 */
	public Class getBeanClass() {
		return beanClass_;
	}

	public boolean hasProperty(String propertyName) {
		return propertyCache_.get(propertyName) != null;
	}

	/**
	 * @see beans.BeanDesc#getProperty(java.lang.String)
	 */
	public Property getProperty(String propertyName)
		throws DaoException {

		Property pd = (Property) propertyCache_.get(propertyName);
		if (pd == null) {
			throw new DaoException(DaoException.PROPERTY_NOT_FOUND,
					new Object[]{beanClass_,propertyName});
		}
		return pd;
	}
	
	private Property getPropertyFromCache(String propertyName) {
		return (Property) propertyCache_.get(propertyName);
	}

	/**
	 * @see beans.BeanDesc#getProperty(int)
	 */
	public Property getProperty(int index) {
		return (Property) propertyCache_.get(index);
	}

	/**
	 * @see beans.BeanDesc#getPropertySize()
	 */
	public int getPropertySize() {
		return propertyCache_.size();
	}
	
	/**
	 * @see beans.BeanDesc#hasField(java.lang.String)
	 */
	public boolean hasField(String fieldName) {
		return fieldCache_.get(fieldName) != null;
	}
	
	/**
	 * @see beans.BeanDesc#getField(java.lang.String)
	 */
	public Field getField(String fieldName) {
		Field field = (Field) fieldCache_.get(fieldName);
		if (field == null) {
			throw new ReflectException(ReflectException.FIELD_NOT_FOUND,new Object[]{beanClass_, fieldName});
		}
		return field;
	}

	/**
	 * @see beans.BeanDesc#newInstance(java.lang.Object[])
	 */
	public Object newInstance(Object[] args)
		throws ReflectException {

		Constructor constructor = getSuitableConstructor(args);
		return ConstructorUtil.newInstance(constructor, args);
	}

	/**
	 * @see beans.BeanDesc#invoke(java.lang.Object, java.lang.String, java.lang.Object[])
	 */
	public Object invoke(Object target, String methodName, Object[] args) {
		Method method = getSuitableMethod(methodName, args);
		return MethodUtil.invoke(method, target, args);
	}

	/**
	 * @see beans.BeanDesc#getSuitableConstructor(java.lang.Object[])
	 */
	public Constructor getSuitableConstructor(Object[] args)
		throws ReflectException {

		if (args == null) {
			args = EMPTY_ARGS;
		}
		Constructor constructor = findSuitableConstructor(args);
		if (constructor != null) {
			return constructor;
		}
		constructor = findSuitableConstructorAdjustNumber(args);
		if (constructor != null) {
			return constructor;
		}
		throw new ReflectException(ReflectException.CONSTRUCTOR_NOT_FOUND2,new Object[]{beanClass_, args});
	}
	
	/**
	 * @see beans.BeanDesc#getMethods(java.lang.String)
	 */
	public Method[] getMethods(String methodName)
		throws ReflectException {

		Method[] methods = (Method[]) methodsCache_.get(methodName);
		if (methods == null) {
			throw new ReflectException(ReflectException.METHOD_NOT_FOUND,new Object[]{beanClass_, methodName});
		}
		return methods;
	}
	
	/**
	 * @see beans.BeanDesc#hasMethod(java.lang.String)
	 */
	public boolean hasMethod(String methodName) {
		return methodsCache_.get(methodName) != null;
	}
	
	public String[] getMethodNames() {
		return (String[]) methodsCache_.keySet().toArray(new String[methodsCache_.size()]);
	}
	
	private Constructor findSuitableConstructor(Object[] args) {
		outerLoop : for (int i = 0; i < constructors_.length; ++i) {
			Class[] paramTypes = constructors_[i].getParameterTypes();
			if (paramTypes.length != args.length) {
				continue;
			}
			for (int j = 0; j < args.length; ++j) {
				if (args[j] == null
					|| ClassUtil.isAssignableFrom(
						paramTypes[j],
						args[j].getClass())) {
					continue;
				}
				continue outerLoop;
			}
			return constructors_[i];
		}
		return null;
	}
	
	private Constructor findSuitableConstructorAdjustNumber(Object[] args) {
		outerLoop : for (int i = 0; i < constructors_.length; ++i) {
			Class[] paramTypes = constructors_[i].getParameterTypes();
			if (paramTypes.length != args.length) {
				continue;
			}
			for (int j = 0; j < args.length; ++j) {
				if (args[j] == null
					|| ClassUtil.isAssignableFrom(
						paramTypes[j],
						args[j].getClass())
					|| adjustNumber(paramTypes, args, j)) {
					continue;
				}
				continue outerLoop;
			}
			return constructors_[i];
		}
		return null;
	}

	private static boolean adjustNumber(
		Class[] paramTypes,
		Object[] args,
		int index) {

		if (paramTypes[index].isPrimitive()) {
			if (paramTypes[index] == int.class) {
				args[index] = TypeUtil.toInteger(args[index]);
				return true;
			} else if (paramTypes[index] == double.class) {
				args[index] = TypeUtil.toDouble(args[index]);
				return true;
			} else if (paramTypes[index] == long.class) {
				args[index] = TypeUtil.toLong(args[index]);
				return true;
			} else if (paramTypes[index] == short.class) {
				args[index] = TypeUtil.toShort(args[index]);
				return true;
			} else if (paramTypes[index] == float.class) {
				args[index] = TypeUtil.toFloat(args[index]);
				return true;
			}
		} else {
			if (paramTypes[index] == Integer.class) {
				args[index] = TypeUtil.toInteger(args[index]);
				return true;
			} else if (paramTypes[index] == Double.class) {
				args[index] = TypeUtil.toDouble(args[index]);
				return true;
			} else if (paramTypes[index] == Long.class) {
				args[index] = TypeUtil.toLong(args[index]);
				return true;
			} else if (paramTypes[index] == Short.class) {
				args[index] = TypeUtil.toShort(args[index]);
				return true;
			} else if (paramTypes[index] == Float.class) {
				args[index] = TypeUtil.toFloat(args[index]);
				return true;
			}
		}
		return false;
	}

	private void setupPropertys() {
		Method[] methods = beanClass_.getMethods();
		for (int i = 0; i < methods.length; i++) {
			Method m = methods[i];
			String methodName = m.getName();
			if (methodName.startsWith("get")) {
				if (m.getParameterTypes().length != 0
					|| methodName.equals("getClass")) {
					continue;
				}
				String propertyName =
					decapitalizePropertyName(methodName.substring(3));
				setupReadMethod(m, propertyName);
			} else if (methodName.startsWith("is")) {
				if (m.getParameterTypes().length != 0
					|| !m.getReturnType().equals(Boolean.TYPE)) {
					continue;
				}
				String propertyName =
					decapitalizePropertyName(methodName.substring(2));
				setupReadMethod(m, propertyName);
			} else if (methodName.startsWith("set")) {
				if (m.getParameterTypes().length != 1
					|| methodName.equals("setClass")) {
					continue;
				}
				String propertyName =
					decapitalizePropertyName(methodName.substring(3));
				setupWriteMethod(m, propertyName);
			}
		}
		for (Iterator i = invalidPropertyNames_.iterator(); i.hasNext(); ) {
			propertyCache_.remove(i.next());
		}
		invalidPropertyNames_.clear();
	}

	private static String decapitalizePropertyName(String name) {
		if (StringUtil.isEmpty(name)) {
			return name;
		}
		if (name.length() > 1
			&& Character.isUpperCase(name.charAt(1))
			&& Character.isUpperCase(name.charAt(0))) {

			return name;
		}
		char chars[] = name.toCharArray();
		chars[0] = Character.toLowerCase(chars[0]);
		return new String(chars);
	}

	private void addProperty(Property Property)
		throws DaoException {

		if (Property != null) {
			propertyCache_.put(Property.getPropertyName(), Property);
		}
	}

	private void setupReadMethod(Method readMethod, String propertyName) {
		Class propertyType = readMethod.getReturnType();
		Property prop = getPropertyFromCache(propertyName);
		if (prop != null) {
			if (!prop.getPropertyType().equals(propertyType)) {
				invalidPropertyNames_.add(propertyName);
			} else {
				prop.setReadMethod(readMethod);
			}
		} else {
			prop =
				new Property(
					propertyName,
					propertyType,
					readMethod,
					null,
					this);
			addProperty(prop);
		}
	}

	private void setupWriteMethod(Method writeMethod, String propertyName) {
		Class propertyType = writeMethod.getParameterTypes()[0];
		Property prop = getPropertyFromCache(propertyName);
		if (prop != null) {
			if (!prop.getPropertyType().equals(propertyType)) {
				invalidPropertyNames_.add(propertyName);
			} else {
				prop.setWriteMethod(writeMethod);
			}
		} else {
			prop =
				new Property(
					propertyName,
					propertyType,
					null,
					writeMethod,
					this);
			addProperty(prop);
		}
	}

	private Method getSuitableMethod(String methodName, Object[] args)
		throws ReflectException {

		if (args == null) {
			args = EMPTY_ARGS;
		}
		Method[] methods = getMethods(methodName);
		Method method = findSuitableMethod(methods, args);
		if (method != null) {
			return method;
		}
		method = findSuitableMethodAdjustNumber(methods, args);
		if (method != null) {
			return method;
		}
		throw new ReflectException(ReflectException.METHOD_NOT_FOUND2,new Object[]{beanClass_, methodName, args});
	}
	
	private Method findSuitableMethod(Method[] methods, Object[] args) {
		outerLoop : for (int i = 0; i < methods.length; ++i) {
			Class[] paramTypes = methods[i].getParameterTypes();
			if (paramTypes.length != args.length) {
				continue;
			}
			for (int j = 0; j < args.length; ++j) {
				if (args[j] == null
					|| ClassUtil.isAssignableFrom(
						paramTypes[j],
						args[j].getClass())) {
					continue;
				}
				continue outerLoop;
			}
			return methods[i];
		}
		return null;
	}
		
	private Method findSuitableMethodAdjustNumber(
		Method[] methods, Object[] args) {

		outerLoop : for (int i = 0; i < methods.length; ++i) {
			Class[] paramTypes = methods[i].getParameterTypes();
			if (paramTypes.length != args.length) {
				continue;
			}
			for (int j = 0; j < args.length; ++j) {
				if (args[j] == null
					|| ClassUtil.isAssignableFrom(
						paramTypes[j],
						args[j].getClass())
					|| adjustNumber(paramTypes, args, j)) {
					continue;
				}
				continue outerLoop;
			}
			return methods[i];
		}
		return null;
	}

	private void setupMethods() {
		ArrayMap methodListMap = new ArrayMap();
		Method[] methods = beanClass_.getMethods();
		for (int i = 0; i < methods.length; i++) {
			List list = (List) methodListMap.get(methods[i].getName());
			if (list == null) {
				list = new ArrayList();
				methodListMap.put(methods[i].getName(), list);
			}
			list.add(methods[i]);
		}
		for (int i = 0; i < methodListMap.size(); ++i) {
			List methodList = (List) methodListMap.get(i);
			methodsCache_.put(
				methodListMap.getKey(i),
				methodList.toArray(new Method[methodList.size()]));
		}
	}
	
	private void setupField() {
		Field[] fields = beanClass_.getFields();
		for (int i = 0; i < fields.length; i++) {
			if (Modifier.isStatic(fields[i].getModifiers())) {
				fieldCache_.put(fields[i].getName(), fields[i]);
			}
		}
	}
}